#include "stdafx.h"
#include "LightingEngine.h"
#include "PointLight.h"
#include "..\Camera.h"
#include "..\HUD.h"
#include "..\GraphicsManager.h"
#include "../ScreenQuad.h"
#include "..\..\EdgeMath\VectorHelper.h"
#include "..\..\EdgeContent\ContentManager.h"
#include "..\..\EdgeGameCore\SettingsManager.h"
#include "../RenderTarget.h"
#include "../RenderTargetManager.h"
#include "../SamplerStateManager.h"
#include "../BlendStateManager.h"
#include <d3d11.h>

LightingEngine::LightingEngine(void)
{
	lighting_buffer = new ConstantBuffer<VRAM_LIGHTING>(&lighting, { SHADER_INDEX_PIXEL_SHADER, 4 });
	
	pointLight.gPointLight.position = Vector3(10.f, 0.f, 0.f);
	pointLight.gPointLight.diffuseColor = Vector3(1.f);
	pointLight.gPointLight.range = 100.f;
	pointLight.gPointLight.specularColor = Vector3(1.f);
	pointLight.gPointLight.ambientColor = Vector3(0.1f);

	pointLightBuffer = new ConstantBuffer<VRAM_POINTLIGHT>(&pointLight, { SHADER_INDEX_PIXEL_SHADER, 3 });
	directionalLightBuffer = new ConstantBuffer<VRAM_DIRECTIONALLIGHT>(&directionalLight, { SHADER_INDEX_PIXEL_SHADER, 3 });
	sceneLightingBuffer = new ConstantBuffer<VRAM_LIGHTING>(&lighting, { SHADER_INDEX_VERTEX_SHADER, 4 });

	ContentManager* pContent = ContentManager::GetInstance();
	
	shader_renderPointLights = pContent->LoadShaderPair("Basic2DRenderVShader.cso", "PointLightPS.cso", "Basic2DRenderGShader.cso");
	shader_renderDirLights = pContent->LoadShaderPair("Basic2DRenderVShader.cso", "DirectionalLightPs.cso", "Basic2DRenderGShader.cso");

	int scr_width = SettingsManager::GetInstance()->getSetting("ScreenWidth")._int;
	int scr_height = SettingsManager::GetInstance()->getSetting("ScreenHeight")._int;

	diffuseMap = new RenderTarget(scr_width, scr_height, DXGI_FORMAT_R8G8B8A8_UNORM);
	normalMap = new RenderTarget(scr_width, scr_height, DXGI_FORMAT_R8G8B8A8_UNORM);
	specularMap = new RenderTarget(scr_width, scr_height, DXGI_FORMAT_R8G8B8A8_UNORM);
	lightMap = new RenderTarget(scr_width, scr_height, DXGI_FORMAT_R8G8B8A8_UNORM);
	depthMap = new RenderTarget(scr_width, scr_height, DXGI_FORMAT_R8G8B8A8_UNORM);
	//depthBuffer = new DepthBuffer(GraphicsManager::GetInstance()->getDevice(), scr_width, scr_height);

	world_buffer = new ConstantBuffer<VRAM_WORLD>(&world, { SHADER_INDEX_VERTEX_SHADER, 0 });
	viewProj_buffer = new ConstantBuffer<VRAM_VIEWPROJ>(&viewProj, { SHADER_INDEX_VERTEX_SHADER, 1 });
	lightingViewProj_buffer = new ConstantBuffer<VRAM_VIEWPROJ>(&viewProj, { SHADER_INDEX_PIXEL_SHADER, 1 });

	Box2D screenRegion;
	screenRegion.left = -1;
	screenRegion.right = 1;
	screenRegion.bottom = -1;
	screenRegion.top = 1;
	screenQuad = new ScreenQuad(screenRegion);
}

LightingEngine::~LightingEngine(void)
{
	// unload buffers
	delete lighting_buffer;
	delete viewProj_buffer;
	delete world_buffer;
	delete lightingViewProj_buffer;
	delete directionalLightBuffer;
	delete pointLightBuffer;

	// unload lights
	for (unsigned int i = 0; i < lights.size(); i++)
		delete lights[i];

	// unload shaders
	delete shader_renderPointLights;
	delete shader_renderDirLights;

	// unload render targets
	delete diffuseMap;
	delete normalMap;
	delete specularMap;
	delete lightMap;
	delete depthMap;
//	delete depthBuffer;
	for (unsigned int i = 0; i < 5; i++)
		screenQuad->setResourceView(nullptr, i);

	// unload screen quad
	delete screenQuad;
}

void LightingEngine::RenderLights(void)
{
	GraphicsManager* pGraphics = GraphicsManager::GetInstance();

	// set up buffers
	viewProj.view = Camera::GetInstance()->getView();
	viewProj.viewProj = Camera::GetInstance()->getView() * Camera::GetInstance()->getProj();
	viewProj.gInvViewProj = Camera::GetInstance()->getProj() * Camera::GetInstance()->getView();
	lightingViewProj_buffer->Bind();
	lighting.gCameraPosition = Vector4(Camera::GetInstance()->getWorldPosition(), 1.f);

	// set up render targets
	RenderTargetManager* pRtm = RenderTargetManager::getInstance();
	pRtm->UnbindAll();
	pRtm->SetRenderTarget(lightMap, 0);
	pRtm->setDepthBuffer(pGraphics->getDepthBuffer());
	pRtm->Apply();
	
	GraphicsManager::GetInstance()->ClearBackBuffer(vector_4::transparent);
	GraphicsManager::GetInstance()->ClearDepthBuffer(1.f);

	// set up sampler state
	SamplerStateManager* ssm = SamplerStateManager::GetInstance();
	ssm->setCurrentSamplerState(ssm->samplers.linearWrap, { SHADER_INDEX_PIXEL_SHADER, 0 });

	BlendStateManager::GetInstance()->SetBlendState(BlendStateManager::GetInstance()->blendStates.alphaBlend);
	
	// set resource views
	screenQuad->setResourceView(diffuseMap, 0);
	screenQuad->setResourceView(normalMap, 1);
	screenQuad->setResourceView(specularMap, 2);
	screenQuad->setResourceView(depthMap, 5);
	//screenQuad->setResourceView(depthBuffer, 5);

	for (unsigned int i = 0; i < lights.size(); i++)
	{
		ShaderContainer* temp_shaderContainer = screenQuad->getShaderContainer();
		switch (lights[i]->getType())
		{
		case LightType::Directional:
		{
			DirectionalLight* dir_light = (DirectionalLight*)lights[i];
			directionalLight.gDirectionalLight.ambientColor = dir_light->ambientColor;
			directionalLight.gDirectionalLight.diffuseColor = dir_light->diffuseColor;
			directionalLight.gDirectionalLight.direction = dir_light->direction;
			directionalLight.gDirectionalLight.specularColor = dir_light->specularColor;
			directionalLightBuffer->Bind();
			screenQuad->setShaderContainer(shader_renderDirLights);
			break;
		}
		case LightType::Point:
			{
				PointLight* point_light = (PointLight*)lights[i];

				pointLight.gPointLight.diffuseColor = point_light->diffuseColor;
				pointLight.gPointLight.specularColor = point_light->specularColor;
				pointLight.gPointLight.specularColor = point_light->specularColor;
				pointLight.gPointLight.range = point_light->range;
				pointLight.gPointLight.position = point_light->position;
				pointLight.gPointLight.ambientColor = point_light->ambientColor;

				pointLightBuffer->Bind();
				screenQuad->setShaderContainer(shader_renderPointLights);
			}
			break;
		case LightType::Spot:
			assert("Unimplamented code segment reached!");
			break;
		}
		lighting.gCameraPosition = Vector4(Camera::GetInstance()->getWorldPosition());
		lighting.gCameraPosition.w = 1.f;
		lighting.gCameraDirection = Camera::GetInstance()->getForwardVector();
		lighting.gFarDistance = Camera::GetInstance()->farPlaneDistance;
		lighting.gNearDistance = Camera::GetInstance()->nearPlaneDistance;
		lighting.gView = Camera::GetInstance()->getView();
		lighting_buffer->Bind();

		screenQuad->Draw();
		screenQuad->setShaderContainer(temp_shaderContainer);
	}


	for (int i = 0; i < 6; i++)
		screenQuad->setResourceView(nullptr, i);

	// unset render targets
	RenderTargetManager::getInstance()->UnbindAll();
	
	ssm->setCurrentSamplerState(ssm->samplers.linearClamp, { SHADER_INDEX_PIXEL_SHADER, 0 });
}

void LightingEngine::BeginDrawScene(Camera* camera)
{
	GraphicsManager* pGraphics = GraphicsManager::GetInstance();

	// set up buffers
	viewProj.viewProj = camera->getView() * camera->getProj();
	viewProj.gInvViewProj = Matricies::Invert(viewProj.viewProj);
	viewProj_buffer->Bind();
	// set up render targets
	RenderTargetManager* pRtm = RenderTargetManager::getInstance();
	pRtm->UnbindAll();
	pRtm->SetRenderTarget(diffuseMap, 0);
	pRtm->SetRenderTarget(normalMap, 1);
	pRtm->SetRenderTarget(specularMap, 2);
	pRtm->SetRenderTarget(depthMap, 5);
	//pRtm->setDepthBuffer(depthBuffer);

	pRtm->Apply();

	pGraphics->ClearBackBuffer(vector_4::transparent);
	pGraphics->ClearDepthBuffer(1.f);

	lighting.gCameraPosition = Vector4(Camera::GetInstance()->getWorldPosition());
	lighting.gCameraPosition.w = 1.f;
	lighting.gCameraDirection = Camera::GetInstance()->getForwardVector();
	lighting.gFarDistance = Camera::GetInstance()->farPlaneDistance;
	lighting.gNearDistance = Camera::GetInstance()->nearPlaneDistance;
	lighting.gView = Camera::GetInstance()->getView();
	sceneLightingBuffer->Bind();
}

void LightingEngine::EndDrawScene(void)
{
	// unset render targets
	RenderTargetManager::getInstance()->UnbindAll();
}

void LightingEngine::DrawDiffered(void)
{
	RenderTargetManager* pRtm = RenderTargetManager::getInstance();
	pRtm->SetRenderTarget(GraphicsManager::GetInstance()->getBackBuffer(), 0);
	pRtm->setDepthBuffer(GraphicsManager::GetInstance()->getDepthBuffer());
	pRtm->Apply();
	GraphicsManager::GetInstance()->ClearDepthBuffer(1.f);
	
	screenQuad->setResourceView(lightMap, 0);
	BlendStateManager::GetInstance()->SetBlendState(BlendStateManager::GetInstance()->blendStates.alphaBlend);
	screenQuad->getShaderContainer()->Bind();
	screenQuad->Draw();
}

/*static*/ LightingEngine* LightingEngine::GetInstance(void)
{
	static LightingEngine instance;

	return &instance;
}

PointLight* LightingEngine::getLightIndex(const int index)
{
	assert(index < lights.size() && "Index cannot be greater than, or equal to MAX_LIGHTS");

	return (PointLight*)lights[index];
}